#ifndef HMS_EXPORTER_HPP
#define HMS_EXPORTER_HPP

#include "probe.hpp"
#include "solverBase.hpp"
#include "vtkExport.hpp"

#include <filesystem>
#include <experimental/memory>

namespace hms
{
using std::experimental::observer_ptr;
using std::experimental::make_observer;
namespace fs = std::filesystem;
namespace xml = tinyxml2;

template<typename _SolverType>
class Exporter
{
public:
	using SolverType = _SolverType;
	bool enabled {false};
private:
	observer_ptr<const SolverType > m_solver     {nullptr};
	observer_ptr<const std::string> m_caseName   {nullptr};
	mutable observer_ptr<xml::XMLDocument > m_bindingDoc {nullptr};
	observer_ptr<const fs::path>
		m_caseDir {nullptr},
		m_bindingFilePath {nullptr};
	std::vector<Probe<SolverType>> m_probes;

public:
	Exporter() = default;

	Exporter(
		bool useExport,
		const fs::path& caseDir,
		const std::string& caseName,
		const SolverBase<SolverType>& solver,
		xml::XMLDocument& bindingDoc,
		const fs::path& bindingFilePath,
		std::vector<ProbeSettings>&&
	);

	void caseDir( const fs::path& );
	auto caseDir() const -> const fs::path&;

	void caseName( const std::string& );
	auto caseName() const -> const std::string&;

	void solver( const SolverBase<SolverType>& );
	auto solver() const -> const SolverType&;

	void bindingDoc( xml::XMLDocument& );
	auto bindingDoc() const -> xml::XMLDocument&;

	void bindingFilePath( const fs::path& );
	auto bindingFilePath() const -> const fs::path&;

	void probes( std::vector<Probe<SolverType>>&& );
	void probes( std::vector<ProbeSettings>&& );
	auto probes() const -> const std::vector<Probe<SolverType>>&;

	void operator() (){
		if (enabled)
			vtk::timestepToVtk(
				caseDir(), caseName(), solver(),
				&( bindingDoc() ), bindingFilePath()
			);
		for ( const Probe<SolverType>& probe : probes() ){
			probe.write();
		}
	}
};

#define CHEAD(RET) \
template<typename SolverType> RET Exporter<SolverType>::

CHEAD()
Exporter(
	bool useExport,
	const fs::path& caseDirArg,
	const std::string& caseNameArg,
	const SolverBase<SolverType>& solverArg,
	xml::XMLDocument& bindingDocArg,
	const fs::path& bindingFilePathArg,
	std::vector<ProbeSettings>&& probesArg
) :
	enabled {useExport}
{
	caseDir(caseDirArg);
	caseName(caseNameArg);
	solver(solverArg);
	bindingDoc(bindingDocArg);
	bindingFilePath(bindingFilePathArg);
	probes( std::move(probesArg) );
}

#define HMS_GETTER(MEMBER, TYPE) \
CHEAD(auto) MEMBER() const -> TYPE { \
	assert( m_##MEMBER ); \
	return *m_##MEMBER; \
}

#define HMS_SETTER(MEMBER, TYPE) \
CHEAD(void) MEMBER( TYPE arg ){ m_##MEMBER = make_observer(&arg); }

#define HMS_GETTER_SETTER(MEMBER, TYPE) \
HMS_GETTER(MEMBER, TYPE) \
HMS_SETTER(MEMBER, TYPE)

HMS_GETTER_SETTER(caseDir, const fs::path&)
HMS_GETTER_SETTER(caseName, const std::string&)
HMS_GETTER_SETTER(bindingFilePath, const fs::path&)
HMS_GETTER_SETTER(bindingDoc, xml::XMLDocument&)

HMS_GETTER(solver, const SolverType&)
CHEAD(void) solver(const SolverBase<SolverType>& arg){
	m_solver = make_observer( &( arg.derived() ) );
}

CHEAD(void) probes( std::vector<Probe<SolverType>>&& arg ){
	if ( !m_probes.empty() )
		solver().logger().warn("Overwriting probes!");
	m_probes = std::move(arg);
}
CHEAD(void) probes( std::vector<ProbeSettings>&& settings ){
	m_probes.reserve( settings.size() );
	for ( ProbeSettings& s : settings )
		m_probes.emplace_back( std::move(s), caseDir(), solver() );
}
CHEAD(auto) probes() const -> const std::vector<Probe<SolverType>>& {
	return m_probes;
}


#undef HMS_GETTER
#undef HMS_SETTER
#undef HMS_GETTER_SETTER
#undef CHEAD

} // namespace hms

#endif